<!--
 * @Author: Betty
 * @Date: 2020-11-11 21:37:08
 * @LastEditors: Betty
 * @LastEditTime: 2021-02-05 23:29:41
 * @Description: 把对输入的验证抽象出来，封装了几种验证方式，还有做了v-model双向绑定，可以绑定默认属性
-->
<template>
  <div class="validate-input-container pb-3">
    <!-- 绑定邮箱当前的值到ref的val属性，这样好判断值有没有问题，
        并且在blur事件的时候触发验证 -->
    <!-- 使用v-bind="$attrs"把父组件使用子组件时，写在子组件标签上的属性加到这个元素上 -->
    <input
    v-if="tag !== 'textarea'"
    @blur="validateInput"
    :class="{'is-invalid': inputRef.error}"
    class="form-control"
    v-bind="$attrs"
    autocomplete="new-password"
    v-model="inputRef.val"
    />
    <!-- 添加一个textarea标签，在tag为'textarea'的时候显示 -->
    <textarea 
    v-else
    @blur="validateInput"
    :class="{'is-invalid': inputRef.error}"
    class="form-control"
    v-bind="$attrs"
    autocomplete="new-password"
    v-model="inputRef.val"
    ></textarea>
    <div v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</div>
  </div>
</template>

<script lang='ts'>
import { defineComponent, reactive, PropType, onMounted, inject, computed } from 'vue'
import { emitter } from './ValidateForm.vue'
// 验证邮箱的正则表达式
const emailReg = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
// 定义规则类型
export interface RuleProp {
  // type是这几个字面量之一
  type: 'required' | 'email' | 'minLength' | 'maxLength' | 'custom';
  message: string;
  value?: number;
  // 新增属性validator，类型是返布尔类型的函数，用来做自定义验证函数
  validator?: () => boolean;
}
// 给这个类型的数组起别名为规则数组类型
export type RulesProp = RuleProp[]
// 定义标签的类型，目前的值要么是input，要么是textarea
export type TagType = 'input' | 'textarea'

export default defineComponent({
  name: '',
  props: {
    // 这个组件的prop是一个规则数组
    rules: {
      type: Array as PropType<RulesProp>,
      required: false
    },
    // 加上一个叫modelValue的属性，作为input的初始值
    modelValue: String,
    // 让用户传入标签类型
    tag: {
      type: String as PropType<TagType>,
      default: 'input'
    }
  },
  // 不把属性继承到根组件上
  inheritAttrs: false,
  setup(props, context) {
    console.log(context.attrs)
    // 创建一个响应式对象和验证函数，响应式对象存储的是要验证的东西当前的状态，验证函数会在适当的时机修改它的状态
    const inputRef = reactive({
      // 计算属性可以写成里面有get方法和set方法的对象，
      // get方法是怎样计算来它的值，set方法是指定了设置它的值时可以做什么额外操作
      val: computed({
        get: () => props.modelValue ||'',
        set: val => { context.emit('uploaded:modelValue', val) }
      }),
      error: false,
      message: ''
    })
    // watch来监听modelValue，如果拿到了新的值，就手动给inputRef.value赋值
    // watch(() => props.modelValue, (newValue) => {
    //   console.log('watch triggered')
    //   inputRef.val = newValue || ''
    // })
    // 验证函数
    const validateInput = () => {
      // 先判断规则是否存在
      if (props.rules) {
        // 循环数组，处理验证规则，是要每一项都返回true的时候才返回true，否则就是false
        // 所以应该用数组的every方法
        const allPassed = props.rules.every((rule) => {
          // 是否通过验证，假设通过
          let passed = true
          // 让它当前的提示设为这个验证规则的提示
          inputRef.message = rule.message
          switch (rule.type) {
            // 判断是否为空
            case 'required': {
              passed = inputRef.val.trim() !== ''
              break
            }
            // 判断是否是正确的邮箱地址格式
            case 'email': {
              passed = emailReg.test(inputRef.val)
              break
            }
            // 判断最小位数，这时候需要通过value来指定多少位
            case 'minLength': {
              if (rule.value) {
                passed = inputRef.val.trim().length >= rule.value
              } else {
                alert('您好像想要限制最小长度，请设置长度值')
              }
              break
            }
            // 判断最大位数，这时候需要通过value来指定多少位
            case 'maxLength': {
              if (rule.value) {
                passed = inputRef.val.trim().length <= rule.value
              } else {
                alert('您好像想要限制最大长度，请设置长度值')
              }
              break
            }
            // 判断自定义验证函数是否通过
            case 'custom': {
              passed = rule.validator ? rule.validator() : true
              break
            }
            default:
              break
          }
          // 返回这个规则是否有通过（如果有一个不通过，则马上停止判断，外层函数返回false，都通过则外层返回true）
          return passed
        })
        // inputRef的error取决于验证规则是否都通过了。
        inputRef.error = !allPassed
        // 如果有验证规则，那就返回总体的验证结果
        return allPassed
      }
      // 如果没有验证，那就返回true，表示肯定通过验证
      return true
    }
    // 我们用计算属性就可以完成，不需要监听事件，然后来更新了
    // 更新值的方法，参数是事件对象，我们可以确定是键盘输入的事件
    // const updateValue = (e: KeyboardEvent) => {
    //   console.log('watch triggered')
    //   // 拿到这个输入框当前最新值
    //   const targetValue = (e.target as HTMLInputElement).value
    //   // 更新reactive的val属性
    //   inputRef.val = targetValue
    //   // 发送自定义事件
    //   context.emit('update:modelValue', targetValue)
    // }
    // 清空input里的内容
    const toClearInput = () => {
      // 先把ref对象里的当前值清空
      inputRef.val = ''
      // 发送自定义事件，通知父组件来更新
      context.emit('update:modelValue', '')
    }
    // 子组件挂载完毕后触发自定义事件，让父组件接收数据
    // 传送的数据是验证函数
    onMounted(() => {
      // 准备好了以后，触发表单组件准备好的事件，传递的不只是验证函数，还有inject拿到的值
      emitter.emit('form-item-created', {
        validator: validateInput,
        formName: inject('formName')
      })
      console.log('input组件表示，我准备好了')
      // 监听清空input的事件
      emitter.on('clear-input', toClearInput)
    })
    return {
      inputRef,
      validateInput
    }
  }
})
</script>

<style>
</style>
